package utils

import (
	"context"                      // 上下文，用于 Redis 操作
	"fmt"                          // 字符串格式化
	"github.com/golang-jwt/jwt/v5" // JWT 库
	"github.com/redis/go-redis/v9" // Redis 客户端
	"go-ecommerce/config"
	"time" // 时间处理
)

// JWTSecret 是用于签名 JWT 的密钥（项目中应存配置文件）
var JWTSecret = []byte("MySecretKey")

// Rdb 是全局 Redis 客户端实例（需要在程序启动时初始化）
var Rdb *redis.Client

// Ctx 是 Redis 使用的上下文（可用于超时控制、取消等）
var Ctx = context.Background()

// InitRedis 初始化 Redis 客户端连接
func InitRedis() {
	Rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379", // Redis 地址
		Password: "",               // Redis 密码，默认无
		DB:       0,                // 使用的 Redis 数据库编号
	})
}

// Claims 自定义 JWT Claims 结构体
type Claims struct {
	UserID               uint   `json:"user_id"`  // 用户 ID，自定义字段
	Username             string `json:"username"` // 用户名，自定义字段
	jwt.RegisteredClaims        // 嵌入标准字段：过期时间、签发时间等
}

// GenerateToken 生成 JWT Token，并将其存入 Redis（覆盖旧 token）
// 参数：userID 用户ID，username 用户名
// 返回：生成的 token 字符串，或错误信息
func GenerateToken(userID uint, username string) (string, error) {
	// 构建 Claims（包含自定义字段和标准字段）
	claims := Claims{
		UserID:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 设置过期时间为 24 小时
			IssuedAt:  jwt.NewNumericDate(time.Now()),                     // 签发时间为当前
			NotBefore: jwt.NewNumericDate(time.Now()),                     // 生效时间为当前
		},
	}

	// 使用 HS256 签名算法和密钥创建 JWT 对象
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// 生成签名字符串
	tokenStr, err := token.SignedString(JWTSecret)
	if err != nil {
		return "", err // 返回错误
	}

	// 将生成的 token 存入 Redis，key 为 token:user:<user_id>
	key := fmt.Sprintf("token:user:%d", userID)
	err = config.Redis.Set(Ctx, key, tokenStr, 24*time.Hour).Err() // 设置过期时间为 24 小时
	if err != nil {
		return "", err // Redis 设置失败
	}

	// 返回 token 字符串
	return tokenStr, nil
}

// ParseToken 解析并验证 JWT Token，并验证 Redis 中是否存在此 token
// 参数：tokenStr 前端传入的 token 字符串
// 返回：claims 用户信息结构体，或错误信息
func ParseToken(tokenStr string) (*Claims, error) {
	// 解析 token，并传入自定义的 Claims 结构体用于反序列化
	token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return JWTSecret, nil // 返回用于验证签名的密钥
	})
	if err != nil || !token.Valid {
		return nil, jwt.ErrTokenInvalidId // Token 无效或过期
	}

	// 类型断言，将 token.Claims 转换为自定义 Claims 类型
	claims, ok := token.Claims.(*Claims)
	if !ok {
		return nil, jwt.ErrTokenInvalidClaims // 类型断言失败
	}

	// 从 Redis 查询当前用户最新的 token
	key := fmt.Sprintf("token:user:%d", claims.UserID)
	storedToken, err := config.Redis.Get(Ctx, key).Result()
	if err != nil {
		return nil, jwt.ErrTokenInvalidId // Redis 查不到或出错，视为无效
	}

	// Redis 中存储的 token 与传入的不一致，则判定为旧 token
	if storedToken != tokenStr {
		return nil, jwt.ErrTokenInvalidId // 说明已被顶替
	}

	// 返回用户信息
	return claims, nil
}
